/****************************************************************************/
/*  File:       PackageManagerGUI.java                                      */
/*  Author:     F. Georges - fgeorges.org                                   */
/*  Date:       2009-06-18                                                  */
/*  Tags:                                                                   */
/*      Copyright (c) 2009 Florent Georges (see end of file.)               */
/* ------------------------------------------------------------------------ */


package org.expath.pkg.saxon;

import java.awt.FileDialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.filechooser.FileFilter;
import javax.swing.table.AbstractTableModel;

/**
 * Graphical frontend to the package manager.
 *
 * @author Florent Georges
 * @date   2009-06-18
 */
public class PackageManagerGUI
        extends JFrame
{
    /**
     * Creates new form PackageManagerGUI.
     */
    public PackageManagerGUI()
    {
        try {
            initComponents();
            myNameValueLbl.setVisible(false);
            myVersionValueLbl.setVisible(false);
            myDirValueLbl.setVisible(false);
            myMenuItemInstall.setEnabled(false);
            myMenuItemDelete.setEnabled(false);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setTitle("EXPath Package Manager");
            setMenuActions();
            // the repo from the env variable
            String repo = System.getenv("EXPATH_REPO");
            if ( repo == null ) {
                warning("$EXPATH_REPO is not defined.  You can open a repo or create a new one.");
            }
            else {
                setRepo(new File(repo));
            }
        }
        catch ( FileNotFoundException ex ) {
            ex.printStackTrace();
            warning("Error: " + ex);
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            fatalError("Error: " + ex);
        }
    }

    private void setRepo(File repo)
            throws FileNotFoundException
                 , PackageException
    {
        myRepoValueLbl.setText("");
        boolean is_first_time = myRepo == null;
        myRepo = new Repository(repo, new GraphicUserInteraction(this));
        myRepoValueLbl.setText(repo.getAbsolutePath());
        if ( is_first_time ) {
            // set the table and the table model (which depends on myRepo)
            myModulesTblModel = new ModuleTableModel();
            myModulesTbl.setModel(myModulesTblModel);
            // configure the selection model on the table
            ListSelectionModel select_model = myModulesTbl.getSelectionModel();
            select_model.addListSelectionListener(new SelectionListener());
            select_model.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            // enable install and delete menu actions
            myMenuItemInstall.setEnabled(true);
            myMenuItemDelete.setEnabled(true);
        }
        refreshTable();
    }

    /**
     * Encapsulate myRepo.getModules(), without throwing any exception.
     *
     * This is for classes implementing Swing interfaces, which do not define
     * exception that can be throwed from the methods.  So this method catch
     * any exception from Repository.getModules(), and die with a message.
     */
    private List<Module> getModulesWithoutException()
    {
        try {
            return myRepo.getModules();
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            fatalError(ex.getMessage());
            // cannot happen, thanks to fatalError(), but compiler needs it
            throw new RuntimeException(ex);
        }
    }

    private File selectFile(String title, boolean xar)
    {
        // Swing version, using JFileChooser, looks terrible on Mac OS X.  But
        // it seems that with the AWT version, using FileDialog, which looks
        // good on Mac OS X, it is not possible to select directories...
        // So if under Mac and selecting a file, use AWT, if not use Swing.
        if ( xar && isUnderMac ) {
            return selectFileAwt(title);
        }
        else {
            return selectFileSwing(title, xar);
        }
    }

    private File selectFileAwt(String title)
    {
        FileDialog dialog = new FileDialog(this, title, FileDialog.LOAD);
        dialog.setFilenameFilter(new ExtensionFilter("X* package", "xar"));
        dialog.setVisible(true);
        String f = dialog.getFile();
        if ( f == null ) {
            return null;
        }
        else {
            return new File(dialog.getDirectory(), f);
        }
    }

    private File selectFileSwing(String title, boolean xar)
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setMultiSelectionEnabled(false);
        if ( xar ) {
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            FileFilter filter = new ExtensionFilter("X* package", "xar");
            chooser.addChoosableFileFilter(filter);
        }
        else {
            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        int res = chooser.showDialog(this, title);
        if ( res == JFileChooser.APPROVE_OPTION ) {
            return chooser.getSelectedFile();
        }
        else {
            return null;
        }
    }

    private void newRepository()
    {
        try {
            File dir = selectFile("New repository", false);
            if ( dir == null ) {
                System.err.println("Package choose dialog canceled by user");
            }
            else if ( dir.exists() ) {
                warning("The directory exists yet: " + dir);
            }
            else {
                // TODO: Move the logic to Repository (IMHO)...
                dir.mkdir();
                ClassLoader loader = getClass().getClassLoader();
                for ( String file : EMPTY_REPO_FILES ) {
                    String rsrc = EMPTY_REPO_PACKAGE + "/" + file;
                    InputStream in = loader.getResourceAsStream(rsrc);
                    OutputStream out = new FileOutputStream(new File(dir, file));
                    byte[] buffer = new byte[1024];
                    int len;
                    while( (len = in.read(buffer)) >= 0 ) {
                        out.write(buffer, 0, len);
                    }
                    in.close();
                    out.close();
                }
                // switch to this new repo
                setRepo(dir);
            }
        }
        catch ( IOException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
    }

    private void openRepository()
    {
        try {
            File dir = selectFile("Open repository", false);
            if ( dir == null ) {
                System.err.println("Package choose dialog canceled by user");
            }
            else if ( ! dir.exists() ) {
                warning("The directory does not exist: " + dir);
            }
            else if ( ! dir.isDirectory() ) {
                warning("The file is not a directory: " + dir);
            }
            else {
                setRepo(dir);
            }
        }
        catch ( FileNotFoundException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
    }

    private void installPackage()
    {
        try {
            File pkg = selectFile("Install package for Saxon", true);
            if ( pkg == null ) {
                System.err.println("Package choose dialog canceled by user");
            }
            else {
                boolean force = false;
                myRepo.installPackage(pkg, force);
            }
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
        refreshTable();
    }

    private void deletePackage()
    {
        try {
            int idx = myModulesTbl.getSelectedRow();
            if ( idx < 0 ) {
                warning("No package selected.");
            }
            else {
                Module m = myModules.get(idx);
                myRepo.deleteModule(m);
            }
        }
        catch ( PackageException ex ) {
            ex.printStackTrace();
            warning(ex.getMessage());
        }
        refreshTable();
    }

    private void refreshTable()
    {
        myModules = getModulesWithoutException();
        myModulesTbl.tableChanged(new TableModelEvent(myModulesTblModel));
        myModulesTbl.repaint();
    }

    private void warning(String msg)
    {
        int type = JOptionPane.WARNING_MESSAGE;
        JOptionPane.showMessageDialog(this, "Error: " + msg, "Error", type);
    }

    private void fatalError(String msg)
    {
        int type = JOptionPane.ERROR_MESSAGE;
        JOptionPane.showMessageDialog(this, "Error: " + msg, "Error", type);
        System.exit(1);
    }

    private void setMenuActions()
    {
        myMenuItemNew.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                newRepository();
            }
        });
        myMenuItemOpen.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                openRepository();
            }
        });
        myMenuItemInstall.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                installPackage();
            }
        });
        myMenuItemDelete.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                deletePackage();
            }
        });
        myMenuItemQuit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
    }

    private Repository myRepo;
    private List<Module> myModules = null;
    private ModuleTableModel myModulesTblModel;

    private static final boolean isUnderMac = "Mac OS X".equals(System.getProperty("os.name"));
    private static final String EMPTY_REPO_PACKAGE = "org/expath/pkg/saxon/repo";
    private static final String[] EMPTY_REPO_FILES = {
        "catalog.xml",
        "empty-java-wrapper.xsl",
        "empty-wrappers-cat.xml",
        "java-wrappers-cat.xml",
        "modules.xml"
    };

    /**
     * Depends on myRepo.
     */
    private class ModuleTableModel
            extends AbstractTableModel
    {
        @Override
        public String getColumnName(int idx) {
            String[] names = {"Dir", "Name"};
            return names[idx];
        }
        public int getRowCount() {
            return myModules.size();
        }
        public int getColumnCount() {
            return 2;
        }
        public Object getValueAt(int row, int col) {
            Module m = myModules.get(row);
            return col == 0 ? m.getDirectory().getName() : m.getTitle();
        }
    }

    /**
     * Depends on myRepo.
     */
    private class SelectionListener
            implements ListSelectionListener
    {
        public void valueChanged(ListSelectionEvent e) {
            if ( ! e.getValueIsAdjusting() ) {
                ListSelectionModel sel = (ListSelectionModel) e.getSource();
                int idx = sel.getAnchorSelectionIndex();
                if ( idx < 0 ) {
                    myNameValueLbl.setVisible(false);
                    myVersionValueLbl.setVisible(false);
                    myDirValueLbl.setVisible(false);
                }
                else {
                    Module m = myModules.get(idx);
                    myNameValueLbl.setText(m.getTitle());
                    myNameValueLbl.setVisible(true);
                    myVersionValueLbl.setText(m.getVersion());
                    myVersionValueLbl.setVisible(true);
                    myDirValueLbl.setText(m.getDirectory().getAbsolutePath());
                    myDirValueLbl.setVisible(true);
                }
            }
        }
    }

    private static class ExtensionFilter
            extends FileFilter
            implements FilenameFilter
    {
        public ExtensionFilter(String desc, String ext) {
            myDesc = desc;
            myExt = "." + ext;
        }
        public boolean accept(File dir, String name) {
            return name.endsWith(myExt);
        }
        public boolean accept(File f) {
            return f.getName().endsWith(myExt);
        }
        public String getDescription() {
            return myDesc;
        }
        private String myDesc;
        private String myExt;
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * 
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        myRepoLbl = new javax.swing.JLabel();
        myRepoValueLbl = new javax.swing.JLabel();
        myModulesLbl = new javax.swing.JLabel();
        myModulesTableScrl = new javax.swing.JScrollPane();
        myModulesTbl = new javax.swing.JTable();
        myNameLbl = new javax.swing.JLabel();
        myVersionLbl = new javax.swing.JLabel();
        myDirLbl = new javax.swing.JLabel();
        myDirValueLbl = new javax.swing.JLabel();
        myVersionValueLbl = new javax.swing.JLabel();
        myNameValueLbl = new javax.swing.JLabel();
        myMenuBar = new javax.swing.JMenuBar();
        myMenuFile = new javax.swing.JMenu();
        myMenuItemNew = new javax.swing.JMenuItem();
        myMenuItemOpen = new javax.swing.JMenuItem();
        myMenuItemInstall = new javax.swing.JMenuItem();
        myMenuItemDelete = new javax.swing.JMenuItem();
        myMenuItemQuit = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        myRepoLbl.setText("Repo:");

        myModulesLbl.setText("Modules:");

        myModulesTbl.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {

            }
        ));
        myModulesTableScrl.setViewportView(myModulesTbl);

        myNameLbl.setText("Name:");
        myNameLbl.setFocusable(false);

        myVersionLbl.setText("Version:");
        myVersionLbl.setFocusable(false);

        myDirLbl.setText("Directory:");
        myDirLbl.setFocusable(false);

        myDirValueLbl.setText("...");
        myDirValueLbl.setFocusable(false);

        myVersionValueLbl.setText("...");
        myVersionValueLbl.setFocusable(false);

        myNameValueLbl.setText("...");
        myNameValueLbl.setFocusable(false);

        myMenuFile.setText("File");

        myMenuItemNew.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_N, java.awt.event.InputEvent.CTRL_MASK));
        myMenuItemNew.setText("New repository...");
        myMenuFile.add(myMenuItemNew);

        myMenuItemOpen.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, java.awt.event.InputEvent.CTRL_MASK));
        myMenuItemOpen.setText("Open repository...");
        myMenuFile.add(myMenuItemOpen);

        myMenuItemInstall.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_I, java.awt.event.InputEvent.CTRL_MASK));
        myMenuItemInstall.setText("Install package...");
        myMenuFile.add(myMenuItemInstall);

        myMenuItemDelete.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, java.awt.event.InputEvent.CTRL_MASK));
        myMenuItemDelete.setText("Delete package");
        myMenuFile.add(myMenuItemDelete);

        myMenuItemQuit.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Q, java.awt.event.InputEvent.CTRL_MASK));
        myMenuItemQuit.setText("Quit");
        myMenuFile.add(myMenuItemQuit);

        myMenuBar.add(myMenuFile);

        setJMenuBar(myMenuBar);

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(myModulesTableScrl, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 565, Short.MAX_VALUE)
                    .add(layout.createSequentialGroup()
                        .add(myRepoLbl)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(myRepoValueLbl))
                    .add(myModulesLbl)
                    .add(layout.createSequentialGroup()
                        .add(myDirLbl)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                        .add(myDirValueLbl, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 489, Short.MAX_VALUE))
                    .add(layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(myNameLbl)
                            .add(myVersionLbl))
                        .add(25, 25, 25)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(myNameValueLbl, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 489, Short.MAX_VALUE)
                            .add(myVersionValueLbl, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 489, Short.MAX_VALUE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(myRepoLbl)
                    .add(myRepoValueLbl))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(myModulesLbl)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(myModulesTableScrl, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 261, Short.MAX_VALUE)
                .add(14, 14, 14)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(myNameValueLbl)
                    .add(myNameLbl))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(myVersionLbl)
                    .add(myVersionValueLbl))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(myDirLbl)
                    .add(myDirValueLbl))
                .add(20, 20, 20))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    /**
    * @param args the command line arguments
    */
    public static void main(String args[])
            throws UnsupportedLookAndFeelException
                 , IllegalAccessException
                 , InstantiationException
                 , ClassNotFoundException
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new PackageManagerGUI().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel myDirLbl;
    private javax.swing.JLabel myDirValueLbl;
    private javax.swing.JMenuBar myMenuBar;
    private javax.swing.JMenu myMenuFile;
    private javax.swing.JMenuItem myMenuItemDelete;
    private javax.swing.JMenuItem myMenuItemInstall;
    private javax.swing.JMenuItem myMenuItemNew;
    private javax.swing.JMenuItem myMenuItemOpen;
    private javax.swing.JMenuItem myMenuItemQuit;
    private javax.swing.JLabel myModulesLbl;
    private javax.swing.JScrollPane myModulesTableScrl;
    private javax.swing.JTable myModulesTbl;
    private javax.swing.JLabel myNameLbl;
    private javax.swing.JLabel myNameValueLbl;
    private javax.swing.JLabel myRepoLbl;
    private javax.swing.JLabel myRepoValueLbl;
    private javax.swing.JLabel myVersionLbl;
    private javax.swing.JLabel myVersionValueLbl;
    // End of variables declaration//GEN-END:variables
}


/* ------------------------------------------------------------------------ */
/*  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               */
/*                                                                          */
/*  The contents of this file are subject to the Mozilla Public License     */
/*  Version 1.0 (the "License"); you may not use this file except in        */
/*  compliance with the License. You may obtain a copy of the License at    */
/*  http://www.mozilla.org/MPL/.                                            */
/*                                                                          */
/*  Software distributed under the License is distributed on an "AS IS"     */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See    */
/*  the License for the specific language governing rights and limitations  */
/*  under the License.                                                      */
/*                                                                          */
/*  The Original Code is: all this file.                                    */
/*                                                                          */
/*  The Initial Developer of the Original Code is Florent Georges.          */
/*                                                                          */
/*  Contributor(s): none.                                                   */
/* ------------------------------------------------------------------------ */
